package com.paw.gateway.filter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.paw.common.constant.CommonConstant;
import com.paw.gateway.domain.HttpRequestFilterDto;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class TraceFilter implements GlobalFilter, GatewayFilter, Ordered {


  /**
   * httpheader，traceId的key名称
   */
//  private static final String TRACE_ID = "traceId";

  private static final String CONTENT_TYPE = "Content-Type";

  private static final String CONTENT_TYPE_JSON = "application/json";

  private static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";

  private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute";

  final String START_TIME = "START_TIME";


  @Override
  public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    //判断过滤器是否执行
    String requestUrl = request.getURI().getPath();
    AntPathMatcher pathMatcher = new AntPathMatcher();
    exchange.getAttributes().put(START_TIME, System.currentTimeMillis());

    // MDC  traceId
    String traceId = UUID.fastUUID().toString(true);
    MDC.put(CommonConstant.TRACE_ID,traceId);
    String requestId = request.getHeaders().getFirst(CommonConstant.REQUEST_ID);
    MDC.put(CommonConstant.REQUEST_ID,requestId);
    
    Mono<Void> afterMono = Mono.fromRunnable(() -> {
      Long startTime = exchange.getAttribute(START_TIME);
      if (startTime != null) {
        Long executeTime = (System.currentTimeMillis() - startTime);
        log.info("request time: " + exchange.getRequest().getURI().getRawPath() + " : " + executeTime + "ms");
      }
      // 清除MDC
      MDC.clear();
    });


    if (pathMatcher.match("/api/**", requestUrl)) {
      String bodyStr = "";
      String contentType = request.getHeaders().getFirst(CONTENT_TYPE);
      String method = request.getMethodValue();
      boolean isPostFormUrl = HttpMethod.POST.name().equalsIgnoreCase(method) && contentType!=null && contentType.contains(CONTENT_TYPE_FORM_URL);
      boolean isPostJson = HttpMethod.POST.name().equalsIgnoreCase(method) && contentType!=null && contentType.contains(CONTENT_TYPE_JSON);

      //判断是否为POST请求
      if (isPostJson||isPostFormUrl) {
        ServerRequest serverRequest = new DefaultServerRequest(exchange);
        List<String> traceIdList = new ArrayList<>();
        traceIdList.add(traceId);
        // 读取请求体
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
            .flatMap(body -> {
              Map<String,Object> params = new HashMap<>(16);
              if(isPostJson){
                params = JSON.parseObject(body);
              }else if(isPostFormUrl){
                for (String paramVal : body.split("&")) {
                  String[] paramArr = paramVal.split("=");
                  if(paramArr.length==2){
                    params.put(paramArr[0],paramArr[1]);
                  }
                }
              }
              //记录请求体日志
              getRequestInfo(exchange, params);
              return Mono.just(body);
            });

        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);

        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext())
            .then(Mono.defer(() -> {
              ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                  exchange.getRequest()) {
                @Override
                public HttpHeaders getHeaders () {
                  long contentLength = headers.getContentLength();
                  HttpHeaders httpHeaders = new HttpHeaders();
                  httpHeaders.putAll(super.getHeaders());
                  httpHeaders.put(CommonConstant.TRACE_ID, traceIdList);
                  if (contentLength > 0) {
                    httpHeaders.setContentLength(contentLength);
                  } else {
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                  }
                  return httpHeaders;
                }

                @Override
                public Flux<DataBuffer> getBody () {
                  return outputMessage.getBody();
                }
              };

              return chain.filter(exchange.mutate().request(decorator).build()).then(afterMono);
            }));
      }
      if (HttpMethod.GET.name().equalsIgnoreCase(method)) {
        Map<String,String> requestParamMap = request.getQueryParams().toSingleValueMap();
        Map<String,Object> paramsMap = new HashMap<>(16);

        paramsMap.putAll(requestParamMap);
        getRequestInfo(exchange, paramsMap);
      }
    }

    ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
        .header(CommonConstant.TRACE_ID, traceId).build();
    return chain.filter(exchange.mutate().request(serverHttpRequest).build()).then(afterMono);

  }


  /**
   * 请求日志
   *
   * @param exchange
   * @param requestParameters
   * @return
   */
  private String getRequestInfo (ServerWebExchange exchange, Map<String,Object> requestParameters) {
    log.debug("接口请求参数：{}", requestParameters);
    ServerHttpRequest request = exchange.getRequest();
    String remoteHostAddress = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
    HttpRequestFilterDto dto = new HttpRequestFilterDto();
    Map<String, Object> headerMap = new HashMap<>(16);
    if (request.getHeaders().getFirst(CommonConstant.TOKEN) != null) {
      headerMap.put(CommonConstant.TOKEN, request.getHeaders().getFirst(CommonConstant.TOKEN));
    }
    if (request.getHeaders().getFirst(CommonConstant.REQUEST_ID) != null) {
      headerMap.put(CommonConstant.REQUEST_ID, request.getHeaders().getFirst(CommonConstant.REQUEST_ID));
    }
    if (request.getHeaders().getFirst(CommonConstant.CONTENT_TYPE) != null) {
      headerMap.put(CommonConstant.CONTENT_TYPE, request.getHeaders().getFirst(CommonConstant.CONTENT_TYPE));
    }
    if (request.getHeaders().getFirst(CommonConstant.CONTENT_TYPE) != null) {
      headerMap.put(CommonConstant.CONTENT_TYPE, request.getHeaders().getFirst(CommonConstant.CONTENT_TYPE));
    }

    if (request.getHeaders().getFirst(CommonConstant.USER_AGENT) != null) {
      headerMap.put(CommonConstant.USER_AGENT, request.getHeaders().getFirst(CommonConstant.USER_AGENT));
    }

    dto.setRemoteIp(remoteHostAddress);
    dto.setHeaders(headerMap);
    dto.setUrl(request.getURI().getPath());
    dto.setMethod(request.getMethodValue());
    dto.setParameters(requestParameters);





    Route route = exchange.getAttribute(GATEWAY_ROUTE_BEAN);
//    //是否配置路由
    if (route != null) {
      dto.setRoute(route.getId());
    }
    String msg = JSON.toJSONString(dto);
    log.info("gateway request: {}",JSON.toJSON(dto));
    return msg;
  }

  @Override
  public int getOrder () {
    return -1;
  }
}